﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.Structure;
using paravili.Algorithm;
using paravili.Services;
using Sebarf.Services.Interfaces;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// Groups the cluster in regions of activity
	/// Visualizes the regions of activity
	/// </summary>
	public class GroupClusterIntoRegionsOfActivity : ProcessStepWithMeasurement<IList<Cluster>> {
		#region Public Properties

		[ConfigurableNumericValue(Name = "CloudRadius", RangeFrom = 5, RangeTo = 100)]
		public int CloudRadius { get; set; }

		[ConfigurableNumericValue(Name = "DeGrowingRate", RangeFrom = 1, RangeTo = 5)]
		public int DeGrowingRate { get; set; }

		[ConfigurableNumericValue(Name = "GrowingRate", RangeFrom = 1, RangeTo = 7)]
		public int GrowingRate { get; set; }

		[ServiceRequest(IsOptional = true)]
		public IImageLocatorService ImageLocatorService { get; set; }

		[ConfigurabelStringValue(Name = "ImageProviderId")]
		public String ImageProviderId { get; set; }

		[ConfigurabelBooleanValue(Name = "IsEnable")]
		public bool IsEnable { get; set; }

		[ServiceRequest]
		public IResetHandlerService ResetHandlerService {
			set { value.Reset += OnReset; }
		}

		[ConfigurableLabValue(Name = "color of the hull")]
		public Lab HullColor { get; set; }

		#endregion

		#region Public Methods

		public GroupClusterIntoRegionsOfActivity() {
			CloudRadius = 5;
			GrowingRate = 2;
			DeGrowingRate = 1;
			ImageProviderId = "BgrToLabConverter.ImageData";
			HullColor = new Lab(200, 60, 255);
		}

		#endregion

		#region Private Methods

		private void OnReset(Object sender, EventArgs args) {
			m_clouds.Clear();
		}

		#endregion

		#region Private Fields

		private readonly List<MovementCloud> m_clouds = new List<MovementCloud>();

		#endregion

		protected override IList<Cluster> OnProcess(IList<Cluster> toProcess) {
			if (toProcess != null && IsEnable) {
				foreach (Cluster movement in toProcess) {
					bool couldAdd = false;
					foreach (MovementCloud cloud in m_clouds) {
						if (couldAdd) {
							continue;
						}
						if (cloud.TryToAddMovement(movement, movement.PreviousCluster)) {
							couldAdd = true;
						}
					}
					if (!couldAdd) {
						m_clouds.Add(new MovementCloud(new Point((int)movement.AverageXValue, (int)movement.AverageYValue),
														 new Point((int)movement.PreviousCluster.AverageXValue,
																	(int)movement.PreviousCluster.AverageYValue), CloudRadius));
					}
				}
				foreach (MovementCloud cloud in m_clouds) {
					cloud.CloudRadius = CloudRadius;
					cloud.DeGrowingRate = DeGrowingRate;
					cloud.GrowingRate = GrowingRate;
					cloud.DeGrowIfNobodyAdded();
				}

				m_clouds.RemoveAll(delegate(MovementCloud cloud) { return !cloud.IsLiving; });

				Image<Lab, byte> img = ImageLocatorService.GetImage(ImageProviderId);
				if (img != null) {
					foreach (MovementCloud cloud in m_clouds) {
						img.DrawPolyline(cloud.Points.ToArray(), true, HullColor, 2);
					}
				}
			}

			return toProcess;
		}

		#region Nested type: MovementCloud

		private class MovementCloud {
			public MovementCloud(Point from, Point to, int cloudRadius) {
				CloudRadius = cloudRadius;

				double b_1 = cloudRadius;
				double a_1 = cloudRadius;

				// generate points
				var points = new List<Point>();

				AddPoints(from, b_1, a_1, points);
				AddPoints(to, b_1, a_1, points);
				CouldAddPoints = true;
				LivingPoints = 20;
				Points = new List<Point>(Convexhull.convexhull(points.ToArray()));
			}

			public List<Point> Points { get; set; }

			public int CloudRadius { get; set; }

			public int DeGrowingRate { get; set; }
			public int GrowingRate { get; set; }

			public int LivingPoints { get; set; }

			public bool IsLiving {
				get { return Points.Count > 2; }
			}

			public bool CouldAddPoints { get; set; }

			public void DeGrowIfNobodyAdded() {
				//if (!CouldAddPoints) {
				LivingPoints -= (DeGrowingRate + 1);
				if (LivingPoints < 1) {
					var r = new Random();
					Points.RemoveAt(r.Next(0, Points.Count - 1));
					Points = new List<Point>(Convexhull.convexhull(Points.ToArray()));
				}
				//	}

				//	CouldAddPoints = false;
			}

			public bool TryToAddMovement(Cluster fromCluster, Cluster toCluster) {
				var from = new Point((int)fromCluster.AverageXValue, (int)fromCluster.AverageYValue);
				var to = new Point((int)toCluster.AverageXValue, (int)toCluster.AverageYValue);
				// convex hull must be the same
				var tmpPoints = new List<Point>(Points);
				tmpPoints.Add(from);
				tmpPoints.Add(to);

				Point[] convexHul = Convexhull.convexhull(tmpPoints.ToArray());
				if (convexHul.Length != Points.Count) {
					return false;
				}
				foreach (Point p in convexHul) {
					if (!Points.Contains(p)) {
						return false;
					}
				}
				// from and to are in the cloud, extend cloud
				AddPoints(from, CloudRadius, CloudRadius, Points);
				AddPoints(to, CloudRadius, CloudRadius, Points);

				Points = new List<Point>(Convexhull.convexhull(Points.ToArray()));
				CouldAddPoints = true;
				LivingPoints += GrowingRate;
				return true;
			}

			private void AddPoints(Point sourcePoint, double b_1, double a_1, List<Point> points) {
				for (int i = 0; i < 2; i++) {
					for (int y = 0; y < 2; y++) {
						var copy = new Point(sourcePoint.X, sourcePoint.Y);
						copy.Offset(i % 2 == 0 ? -(int)b_1 : (int)b_1, y % 2 == 0 ? -(int)a_1 : (int)a_1);
						points.Add(copy);
					}
				}
			}
		}

		#endregion
	}
}